#include "particleSystemCS.h"
#include "input.h"

ParticleSystemCS::ParticleSystemCS()
{
    sceneShaderID = "particleCS";
    depthShaderID = "particleCSDepth";

    menuType.push_back("15PickWorldButton");
    menuProp.push_back("MYTARGET");
    menuIcon.push_back("icon_base");
    menuName.push_back("pick");

    force       = .005;
    timeStep    = .0010;
    timeStep2   = timeStep*timeStep;
    diameter    = 8;
    swarmCentre.location    = Vector4f(0,0,0,2);
    swarmCentre.birth       = 100;

    myTarget=NULL;

    textureID="brushTwo";
    registerProperties();
}

ParticleSystemCS::~ParticleSystemCS()
{
    //dtor
}

void ParticleSystemCS::registerProperties(){

 ParticleSystem::registerProperties();
 createMemberID("DIAMETER",&diameter,this);
 createMemberID("TIMESTEP",&timeStep,this);
 createMemberID("FORCE",&force,this);
 createMemberID("MYTARGET",&myTarget,this);

}

void ParticleSystemCS::setup()
{
    ParticleSystem::setup();
    addRandomParticles(100,diameter);
}

void ParticleSystemCS::create(){

    renderer->addActor(this);

}

void ParticleSystemCS::setupPhysics()
{

    // calculate total needed to be setup in case we are adding to current group
    int total = particles.size() - particlesX.size();
    int start = particlesX.size();
    int end   = start+total;


    for( int i = start; i < end; i++)
    {
        ParticleCS p;
        p.oldPos    = Vector3f(particles[i].location.x,particles[i].location.y,particles[i].location.z);
        p.drag      = .99f;
        p.blink     = ((double)rand()/(double)RAND_MAX)  * 1;
        particlesX.push_back(p);
    }

    for( uint i = 0; i < particles.size(); i++)
        particles[i].location.w = .5f;

    for(uint i=0;i<particles.size();i++)
     {
        particles[i].color.x = 1.0f;
        particles[i].color.y = particles[i].color.x;
        particles[i].color.z = particles[i].color.x;
     }

}

void ParticleSystemCS::addRandomParticles(int amount, float diameter)
{

    // create particles
    ParticleSystem::addRandomParticles(amount,diameter);

    // create attractors at random positions in relation to swarm center
    addRandomAttractors(swarmCentre.location.w);

    // setup physics needed
    setupPhysics();
}

void ParticleSystemCS::addRandomAttractors(float d)
{

    int total = particles.size() - attractors.size();
    int start = attractors.size();
    int end   = start+total;

    // make new
    for( int i = start; i < end; i++)
    {
        Particle p;
        float diameter = d ;//- ((double)rand()/(double)RAND_MAX)  * (d/2);
        p.location.x=((double)rand()/(double)RAND_MAX)  * diameter;
        p.location.y=((double)rand()/(float)RAND_MAX)   * diameter;
        p.location.z=((double)rand()/(float)RAND_MAX)   * diameter;
        p.location.w=1.0;
        p.color=particles[i].color;
        p.birth=((double)rand()/(double)RAND_MAX) * 1000;
        attractors.push_back(p);
    }



}

void ParticleSystemCS::trigger( Actor * other )
{

    if (myTarget)
    cout << myTarget->name << endl;


    if(myTarget)
    {
        setAttractorsFromParticles( ( (ParticleSystem*)myTarget)->particles, 10000 );
        cout << "SET FROM DRAWING!" << endl;
    }

}

void ParticleSystemCS::update(double deltaTime)
{


    double dt  = deltaTime*(1.f/60.f);
    solveAttractions(dt);

    // update each simulations
    simRandomAttraction(deltaTime);

    // update physics
    doVerlet();
    ParticleSystem::update(1);



}

void ParticleSystemCS::doVerlet()
{
    for( uint i=0;i<particles.size();i++)
    {
        Vector3f temp;
        temp = Vector3f(particles[i].location.x, particles[i].location.y, particles[i].location.z);

		particles[i].location.x += (temp.x - particlesX[i].oldPos.x) *particlesX[i].drag*particlesX[i].drag  +timeStep2;//particlesX[i].drag + timeStep2;
		particles[i].location.y += (temp.y - particlesX[i].oldPos.y) *particlesX[i].drag*particlesX[i].drag  +timeStep2;//* particlesX[i].drag + timeStep2;
		particles[i].location.z += (temp.z - particlesX[i].oldPos.z) *particlesX[i].drag*particlesX[i].drag  +timeStep2;//* particlesX[i].drag + timeStep2;

		particlesX[i].oldPos = Vector3f(temp.x, temp.y, temp.z);
    }
}

void ParticleSystemCS::solveAttractions(double deltaTime)
{
    float _minDist2 = diameter;

    for( uint i=0;i<particles.size();i++)
    {
        Vector3f loc    = Vector3f(particles[i].location.x,particles[i].location.y,particles[i].location.z);
        Vector3f aloc   = Vector3f(attractors[i].location.x,attractors[i].location.y,attractors[i].location.z);
        Vector3f delta  = loc - aloc;

		float deltaLength2 = delta.lengthSq();
		if(deltaLength2 > _minDist2) deltaLength2 = _minDist2;

		float scale = deltaLength2 / _minDist2;

        delta.normalize();

		particles[i].location.x -= deltaTime*((force*scale)*delta.x);
		particles[i].location.y -= deltaTime*((force*scale)*delta.y);
		particles[i].location.z -= deltaTime*((force*scale)*delta.z);

    }


}

void ParticleSystemCS::simRandomAttraction(double deltaTime)
{

        // birth is actually more like 'life' and decreases over time
        // this allows the center of the swarm to change to a new random position
        if( swarmCentre.birth <= 0 )
        {
            swarmCentre.location.x = ((double)rand()/(double)RAND_MAX)  * diameter;
            swarmCentre.location.y = ((double)rand()/(float)RAND_MAX)   * diameter;
            swarmCentre.location.z = ((double)rand()/(float)RAND_MAX)   * diameter;
            swarmCentre.birth      = ((double)rand()/(double)RAND_MAX) * 5000;

            // decrease life of attractors so particles go towards new position near new center faster
            for( uint i=0;i<attractors.size();i++)
                attractors[i].birth *= .5;
        }

        // degrade life
        swarmCentre.birth -= deltaTime;

        for( uint i=0;i<attractors.size();i++)
            attractors[i].birth -= deltaTime;

        // reset attractor positions
        for( uint i=0;i<attractors.size();i++)
        {
            if( attractors[i].birth <= 0 )
            {
                attractors[i].birth = ((double)rand()/(double)RAND_MAX) * 10000;
                attractors[i].location.x= swarmCentre.location.x + ((double)rand()/(double)RAND_MAX)  * swarmCentre.location.w;
                attractors[i].location.y= swarmCentre.location.y + ((double)rand()/(float)RAND_MAX)   * swarmCentre.location.w;
                attractors[i].location.z= swarmCentre.location.z + ((double)rand()/(float)RAND_MAX)   * swarmCentre.location.w;

            }

        }

}

void ParticleSystemCS::setAttractorsFromParticles( vector<Particle> points, int waitTime)
{

    Matrix4f offset = baseMatrix.inverse() * myTarget->baseMatrix;

    // make more if we have a deficit
    if( points.size() > particles.size() )
    {
        addRandomParticles( points.size()-particles.size(),diameter);
    }

    int total = min(points.size(), particles.size());

    // reset birth of all
    for( int i=0; i<total; i++)
    {
        attractors[i].birth = waitTime;
    }

    // reset this in case we still want to use the random simulation
    swarmCentre.birth = waitTime;

    // set new positions
    for( int i=0; i<total; i++)
    {
        Vector4f pointLoc=Vector4f(points[i].location.x,points[i].location.y,points[i].location.z,1.0);
        attractors[i].location = offset * (pointLoc);
    }

}

void ParticleSystemCS::drawParticles()
{
    glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT);
    //Enable Point sprite textures, Vertex Shader point-size control and the point sprite extension
    glTexEnvf( GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE );
    //glEnable( GL_VERTEX_PROGRAM_POINT_SIZE );
    glEnable( GL_POINT_SPRITE_ARB );

    ParticleSystem::drawParticles();

    glDisable( GL_POINT_SPRITE_ARB );
}

void ParticleSystemCS::drawAttractors()
{
    if(attractors.size() <= 0 ) return;

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

    GLfloat *verts=&attractors[0].location.x;
    GLfloat *colors=&attractors[0].color.r;

    glVertexPointer(4, GL_FLOAT,sizeof(attractors[0]),verts);
    glColorPointer(4, GL_FLOAT,sizeof(attractors[0]),colors);

    glDrawArrays(GL_POINTS,0,attractors.size()-1);   //FIX: doesn't reconnect last element with first one...

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
}

